#include "pch.h"
#include "D3DGraphic.h"
#include "shaders.h"
#include "ErrorCodes.h"
#include "Texture2D.h"
#include "States.h"
#include "D2DGraphics.h"
#include "MeshLoader.h"
#include "D3DBuffer.h"
#include "D3DShape.h"
#include "VertexLayout.h"
#include "primitives.h"

using namespace Platform;

using namespace DXBase;
using namespace DXBase::D3D;
using namespace DXBase::D2D;

D3DGraphic::D3DGraphic(DXContext^ ctxt)
	: mContext(ctxt)
{
	if (!ctxt)
		throw ref new NullReferenceException();
}

void D3DGraphic::Clear(Windows::UI::Color bg)
{
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);

	float fcolor[4];
	DXBase::makeD3D(bg, fcolor);
	Context->m_d3dContext->ClearRenderTargetView(Context->nativeTarget->m_renderTargetView.Get(), fcolor);
}
void D3DGraphic::Flush()
{
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	Context->m_d3dContext->Flush();
}


void D3DGraphic::SetStateSampler(D3D::SamplerState^ state) { SetStateSampler(0, state); }
void D3DGraphic::SetStateSampler(UINT slot, D3D::SamplerState^ state)
{
	if (!state)
		return;
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	state->Context = Context;
	state->Reload();
	Context->m_d3dContext->PSSetSamplers(slot,1,state->mState.GetAddressOf());
}
void D3DGraphic::SetStateSamplers(UINT slot, const Platform::Array<D3D::SamplerState^>^ states)
{
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	if (!states || states->Length == 0)
		return;
	for each (auto b in states)
	{
		if (!b)
			throw ref new Platform::NullReferenceException();
		b->Context = Context;
	}

	auto N = states->Length;
	std::vector<ID3D11SamplerState*> bstates;
	for (UINT i = 0; i < N; i++)
	{
		states[i]->Reload();
		bstates.push_back(states[i]->mState.Get());
	}
	Context->m_d3dContext->PSSetSamplers(slot, N, &bstates[0]);
}
void D3DGraphic::SetStateRasterizer(D3D::RasterizerState^ state)
{
	if (!state)
	{
		Context->m_d3dContext->RSSetState(0);
		return;
	}
	state->Context = Context;
	state->Reload();
	Context->m_d3dContext->RSSetState(state->mState.Get());
}
void D3DGraphic::SetStateBlend(D3D::BlendState^ state, float4 blendFactor, uint32 mask)
{
	if (!state)
	{
		Context->m_d3dContext->OMSetBlendState(0, nullptr, 0);
		return;
	}
	state->Context = Context;
	state->Reload();
	float blends[4] = {blendFactor.x,blendFactor.y,blendFactor.z,blendFactor.w};
	Context->m_d3dContext->OMSetBlendState(state->mState.Get(), blends, mask);
}
void D3DGraphic::SetStateDepthStencil(D3D::DepthStencilState^ state, UINT stencilRef)
{
	if (!state)
	{
		Context->m_d3dContext->OMSetDepthStencilState(0, 0);
		return;
	}
	state->Context = Context;
	state->Reload();
	Context->m_d3dContext->OMSetDepthStencilState(state->mState.Get(), stencilRef);
}

void D3DGraphic::PixelShader::set(D3D::PixelShader^ shader)
{
	if (!shader)
		return;
	if (shader->IsEmpty)
		throw ExHelper::CreateException(ErrorCodes::ShaderEmpty);
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	shader->Context = Context;
	shader->LoadDXData();
	this->pixelShader = shader;
	// Remark: ID3D11ClassInstances not supported in Metro
    Context->m_d3dContext->PSSetShader(shader->mBasicPixelShader.Get(), nullptr,0);
}
void D3DGraphic::VertexShader::set(D3D::VertexShader^ shader)
{
	if (!shader)
		return;
	if (shader->IsEmpty)
		throw ExHelper::CreateException(ErrorCodes::ShaderEmpty);
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	shader->Context = Context;
	shader->LoadDXData();
	this->vertexShader = shader;
	auto ctxt = Context->m_d3dContext;
	ctxt->IASetInputLayout(shader->mBasicInputLayout.Get());
	// Remark: ID3D11ClassInstances not supported in Metro
	ctxt->VSSetShader(shader->mBasicVertexShader.Get(), nullptr, 0);
}
void D3DGraphic::SetConstantBuffer(unsigned slot, ShaderType sdt, D3D::Buffer^ buf)
{
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	if (!buf)
		return;

	buf->Context = Context;
	auto ptr = buf->GetUpdatedBuffer().GetAddressOf();
	auto ctxt = this->Context->m_d3dContext.Get();

	if ((sdt & ShaderType::Vertex) == ShaderType::Vertex)
		ctxt->VSSetConstantBuffers(slot, 1, ptr);
	if ((sdt & ShaderType::Hull) == ShaderType::Hull)
		ctxt->HSSetConstantBuffers(slot, 1, ptr);
	if ((sdt & ShaderType::Domain) == ShaderType::Domain)
		ctxt->DSSetConstantBuffers(slot, 1, ptr);
	if ((sdt & ShaderType::Geometry) == ShaderType::Geometry)
		ctxt->GSSetConstantBuffers(slot, 1, ptr);
	if ((sdt & ShaderType::Pixel) == ShaderType::Pixel)
		ctxt->PSSetConstantBuffers(slot, 1, ptr);
	if ((sdt & ShaderType::Compute) == ShaderType::Compute)
		ctxt->CSSetConstantBuffers(slot, 1, ptr);
}
void D3DGraphic::SetConstantBuffers(unsigned slot, D3D::ShaderType sdt, Windows::Foundation::Collections::IIterable<D3D::Buffer^>^ buf)
{
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);

	std::vector<ID3D11Buffer*> buffers;
	for each (auto b in buf)
	{
		if (!b)
		{
			for each (auto b2 in buf)
			{
				int i = slot++;
				if (!b2) 
					continue;
				SetConstantBuffer(i, sdt, b2);
			}
			return;
		}
		b->Context = Context;
		buffers.push_back(b->GetUpdatedBuffer().Get());
	}
	auto N = buffers.size();
	if (!N)
		return;

	auto ptr = &buffers[0];
	auto ctxt = this->Context->m_d3dContext.Get();

	if ((sdt & ShaderType::Vertex) == ShaderType::Vertex)
		ctxt->VSSetConstantBuffers(slot, N, ptr);
	if ((sdt & ShaderType::Hull) == ShaderType::Hull)
		ctxt->HSSetConstantBuffers(slot, N, ptr);
	if ((sdt & ShaderType::Domain) == ShaderType::Domain)
		ctxt->DSSetConstantBuffers(slot, N, ptr);
	if ((sdt & ShaderType::Geometry) == ShaderType::Geometry)
		ctxt->GSSetConstantBuffers(slot, N, ptr);
	if ((sdt & ShaderType::Pixel) == ShaderType::Pixel)
		ctxt->PSSetConstantBuffers(slot, N, ptr);
	if ((sdt & ShaderType::Compute) == ShaderType::Compute)
		ctxt->CSSetConstantBuffers(slot, N, ptr);
}
void D3DGraphic::SetShaderResource(D3D::ShaderType sdt, D3D::IResource^ res) { SetShaderResource(0, sdt, res); }
void D3DGraphic::SetShaderResource(UINT slot, D3D::ShaderType sdt, D3D::IResource^ res)
{
	if (!res)
		return;
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	
	res->Context = Context;
	auto ctxt = Context->m_d3dContext;
	auto shaderView = res->GetNativeShaderResourceView()->resView.GetAddressOf();
	if ((sdt & ShaderType::Vertex) == ShaderType::Vertex)
		ctxt->VSSetShaderResources(slot, 1, shaderView);
	if ((sdt & ShaderType::Hull) == ShaderType::Hull)
		ctxt->HSSetShaderResources(slot, 1, shaderView);
	if ((sdt & ShaderType::Domain) == ShaderType::Domain)
		ctxt->DSSetShaderResources(slot, 1, shaderView);
	if ((sdt & ShaderType::Geometry) == ShaderType::Geometry)
		ctxt->GSSetShaderResources(slot, 1, shaderView);
	if ((sdt & ShaderType::Pixel) == ShaderType::Pixel)
		ctxt->PSSetShaderResources(slot, 1, shaderView);
	if ((sdt & ShaderType::Compute) == ShaderType::Compute)
		ctxt->CSSetShaderResources(slot, 1, shaderView);
}
void D3DGraphic::SetShaderResources(UINT slot, D3D::ShaderType sdt, const Platform::Array<D3D::IResource^>^ res)
{
	if (!res || res->Length == 0)
		return;
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	for each (auto b in res)
	{
		if (!b)
			throw ref new Platform::NullReferenceException();
		b->Context = Context;
	}

	auto ctxt = Context->m_d3dContext;
	auto N = res->Length;
	std::vector<ID3D11ShaderResourceView*> buffers;
	for (UINT i = 0; i < N; i++)
		buffers.push_back(res[i]->GetNativeShaderResourceView()->resView.Get());

	if ((sdt & ShaderType::Vertex) == ShaderType::Vertex)
		ctxt->VSSetShaderResources(slot, N, &buffers[0]);
	if ((sdt & ShaderType::Hull) == ShaderType::Hull)
		ctxt->HSSetShaderResources(slot, N, &buffers[0]);
	if ((sdt & ShaderType::Domain) == ShaderType::Domain)
		ctxt->DSSetShaderResources(slot, N, &buffers[0]);
	if ((sdt & ShaderType::Geometry) == ShaderType::Geometry)
		ctxt->GSSetShaderResources(slot, N, &buffers[0]);
	if ((sdt & ShaderType::Pixel) == ShaderType::Pixel)
		ctxt->PSSetShaderResources(slot, N, &buffers[0]);
	if ((sdt & ShaderType::Compute) == ShaderType::Compute)
		ctxt->CSSetShaderResources(slot, N, &buffers[0]);
}

void D3DGraphic::Shape::set(D3D::IShape^ shape)
{
	if (!shape)
	{
		this->shape = nullptr;
		return;
	}

	shape->Context = Context;
	shape->LoadDXData();
	this->shape = shape;

	auto ctxt = Context->m_d3dContext;
	ctxt->IASetPrimitiveTopology(makeD3D(shape->Topology));

	if (shape->VerticesBuffer->Size != shape->VerticesStride->Size)
		throw ExHelper::CreateException(ErrorCodes::ShapeVerticesBufferHaveDifferentSize);
	if (shape->VerticesBuffer->Size)
	{
		std::vector<UINT> strides;
		std::vector<UINT> offsets;
		std::vector<ID3D11Buffer*> buffers;
		UINT N = shape->VerticesBuffer->Size;
		for (UINT i = 0; i < N; i++)
		{
			strides.push_back(shape->VerticesStride->GetAt(i));
			offsets.push_back(0);
			buffers.push_back(shape->VerticesBuffer->GetAt(i)->buffer.Get());
		}
		ctxt->IASetVertexBuffers(0, N, &buffers[0], &strides[0], &offsets[0]);
	}
	if (shape->GetIndexCount())
	{
		switch (shape->IndexFormat)
		{
			// the only 2 supported format
		case Format::R16_uint:
		case Format::R32_uint:
			break;
		default:
			throw ref new InvalidArgumentException();
		}
		ctxt->IASetIndexBuffer(shape->IndicesBuffer->buffer.Get(), makeDXGI(shape->IndexFormat), 0);
	}
}
void D3DGraphic::Draw()
{
	if (!shape || shape->GetVertexCount() == 0)
		return;
	Draw(0, shape->GetVertexCount());
}
void D3DGraphic::Draw(UINT start, UINT count)
{
	if (!shape || !vertexShader)
		return;
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	if (start+count > shape->GetVertexCount())
		throw ref new Platform::InvalidArgumentException();
	shape->Validate(vertexShader->VertexLayout);
	Context->m_d3dContext->Draw(start, count);
}
void D3DGraphic::DrawIndexed()
{
	if (!shape)
		return;
	DrawIndexed(shape->GetIndexCount(), 0, 0);
}
void D3DGraphic::DrawIndexed(UINT count, UINT baseIndex, UINT baseVertex)
{
	if (!shape || !vertexShader)
		return;
	if (!Context->Rendering)
		throw ExHelper::CreateException(ErrorCodes::ContextNotRendering);
	if (baseIndex+count > shape->GetIndexCount())
		throw ref new Platform::InvalidArgumentException();
	if (baseVertex > shape->GetVertexCount())
		throw ref new Platform::InvalidArgumentException();
	shape->Validate(vertexShader->VertexLayout);
	Context->m_d3dContext->DrawIndexed(count, baseIndex, baseVertex);
}
